package com.mathdroid.exceptionhandling;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;

import com.mathdroid.gui.CenteredFrame;

/**
 * This ErrorFrame is used to show internal exceptions in an user friendly way.
 * The user has the ability to select all of the error log and copy and paste it
 * for example to a forum or bug report. 
 */
public final class ErrorFrame extends CenteredFrame {
	/**
	 * Default UID.
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * This text is an introduction of the error log.
	 */
	private static final String DEFAULT_TEXT = 
		"SORRY! This should not happen.";
	
	/**
	 * Amount of rows in the text area.
	 */
	private static final int TEXTAREA_ROWS = 20;
	
	/**
	 * Amount of columns in the text area.
	 */
	private static final int TEXTAREA_COLS = 50;
	
	/**
	 * The amount of columns in the grid layout.
	 */
	private static final int GRID_COLUMNS = 3;
	
	/**
	 * The text area for error log output.
	 */
	private final JTextArea ta = new JTextArea(TEXTAREA_ROWS, TEXTAREA_COLS);
	
	/**
	 * Button to select the whole error log.
	 */
	private final JButton selectAll = new JButton("Select all");
	
	/**
	 * Button to copy the selected text to the clipboard.
	 */
	private final JButton copy = new JButton("Copy");
	
	/**
	 * Button to close the window.
	 */
	private final JButton close = new JButton("Close");
	
	/**
	 * The title of this Error Frame.
	 */
	private static final String TITLE = "Error Report";
	
	/**
	 * The weight of "heavy" components.
	 */
	private static final int HEAVY_WEIGHT = 10;
	
	/**
	 * The constraints used in this UI.
	 */
	private final GridBagConstraints c = new GridBagConstraints();
	
	/**
	 * This frame's panel.
	 */
	private final JPanel panel = new JPanel();
	
	/**
	 * Standard constructor.
	 */
	public ErrorFrame() {
		super();
		panel.setLayout(new GridBagLayout());
		this.setAlwaysOnTop(true);
		
		buildScrollPane();
		buildSelectAllButton();
		buildCopyButton();
		buildCloseButton();
		
		this.add(panel);
		this.setTitle(TITLE);
		this.setDefaultCloseOperation(HIDE_ON_CLOSE);
		this.pack();
	}

	/**
	 * Add the close button.
	 */
	private void buildCloseButton() {
		c.gridx = 2;
		this.close.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				setVisible(false);
			}
		});
		panel.add(this.close, c);
	}

	/**
	 * Add the copy button.
	 */
	private void buildCopyButton() {
		c.gridx = 1;
		this.copy.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				new DroidClipboard().copy(getErrorLog());
			}
		});
		panel.add(this.copy, c);
	}

	/**
	 * Add the "Select all" button.
	 */
	private void buildSelectAllButton() {
		c.gridwidth = 1;
		c.weighty = 1;
		c.gridx = 0;
		c.gridy = 1;
		this.selectAll.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				ta.requestFocusInWindow();
				ta.selectAll();
			}
		});
		panel.add(this.selectAll, c);
	}

	/**
	 * Add the scrollpane with textarea.
	 */
	private void buildScrollPane() {
		c.gridx = 0;
		c.gridy = 0;
		c.gridwidth = GRID_COLUMNS;
		c.weightx = 1;
		c.weighty = HEAVY_WEIGHT;
		c.fill = GridBagConstraints.HORIZONTAL;
		this.ta.setEditable(false);
		JScrollPane sp = new JScrollPane(this.ta);
		sp.setHorizontalScrollBarPolicy(
				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		sp.setVerticalScrollBarPolicy(
				ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
		panel.add(sp, c);
		
		this.ta.append(ErrorFrame.DEFAULT_TEXT);
		sp.setPreferredSize(this.ta.getPreferredScrollableViewportSize());
	}

	/**
	 * Get the error log.
	 * @return Error log.
	 */
	public String getErrorLog() {
		return this.ta.getSelectedText();
	}

	/**
	 * Appends an exception to the whole error log.
	 * @param s The reason for the exception. null for unknown reasons.
	 * @param e The thrown exception.
	 */
	public void appendError(final String s, final Exception e) {
		String reason = null;
		
		if (s == null) {
			reason = "Unknown.";
		} else {
			reason = s;
		}
		
		StringBuilder builder = 
			new StringBuilder("\n\n** Reason: " + reason + " **\n\n");
		
		builder.append(e.getMessage());
		for (StackTraceElement ste : e.getStackTrace()) {
			builder.append("\n" + ste);
		}
		
		this.ta.append(builder.toString());
		this.ta.select(0, 0);
	}
}
